﻿# （1）我是quant林，早年供职于上海的量化私募，后于2017年推出的免费Python期货CTP接口开源框架，采用GPLV3开源协议，简单易学，
# 适合学习入门和拓展开发，就在1.0版发布后不久，我的vnapi 1.0（当时不叫vnapi）就遭受了非法金融经营者用1.2万字连载诽谤文的诋毁攻击，
# 他们甚至在诽谤文下方还放上他们自己的涉及非法金融开源软件的广告。
# 从那次诋毁到现在2.10版的重新推出已过了快7年，人生又有几个7年？
# 当初我作为技术创业者从未遇到类似的情况，不知如何应对，2017年7月我离开上海去了重庆，在重庆
# 写出了virtualapi仿真回测（盛立版、CTP版、通联沪深A股Level2版）、vntrader6框架雏形、kucps专业投资软件的代码，
# 每天开发工作完毕，就去万州的小巷点一份小火锅和啤酒，这是我最难忘的回忆，2019年我又回到了上海，
# 在最近刀郎事件发生后，我开始怀疑我当初离开上海的想法和刀郎的想法是一样的。
# 由于非法金融具有伪装性和渗透性，前几年利用开源开源旗号不断侵蚀我国合规金融市场，某币圈网红公开宣传非法金融，
# 其带有非法金融交易功能的软件甚至短暂的上了某期货公司官网，后他们又于2021年11月被上海证监局调查。
# 在过去7年中，我在打击非法金融的战线上贡献了自己的力量，付出了大量时间和精力的代价，说不清楚这样做是为了自救，
# 还是别的原因，但我忠心的希望金融从业者们牢记合规底线，勿给非法金融可乘之机。
# 为记住那段历史，本次发布的vnapi2.10 期货CTP接口底层代码，正是源于2017年4月的1.0开源版本的稍作整理，已升级到最新的CTP版本，
# 1.0版就是被诋毁造谣的那个版本，vnapi改名前的1.0 版本曾被北京大学出版社《python3.x全栈开发》收录并重点介绍，
# 现在vnapi2.0作为一款Python CTP接口的框架，和当初1.0一样，是基于Ctypes技术，简单易学，主要目的是为了为开发者
# 提供一个通俗易懂的python后端开源框架，并非为了提供一款面面俱到的商业产品，适合入门学习和在此基础上继续拓展开发。
# 我还采用PyQt技术开发为前端开发的 vntrader6客户端（https://www.vnpy.cn）， 基于我开发的vnapi的另一个回调版本，
# 面向的是专业机构客户，VNTrader系列可以说在vnapi 1.0后端框架技术上迭代产生的，
# 当初的1.0，和今天发布的2.0，以及VNTrader、Virtualapi产品均属于我和上海量贝软件的原创产品，
# 我于2014年还获得基于AR增强现实技术的国家发明专利一项。
# 我另有正在申请3项量化交易发明专利，这些技术已授予用于上海量贝作为技术积累可用于商业技术开发。
# 也希望谣言发布者们知晓，靠谣言诋毁一个执迷于技术的程序员和技术创业者是不可能成功的。
# 承载着7年的维权经历与梦想，引来了vnapi2.10版本的发布，这个产品定位是作抛砖引玉之用，适合用户学习Python量化和扩展功能，
# 而非堆砌功能，请各位以宽容的眼光去对待他。
# （2）vnapi 遵循 开源协议GPL v3
# 简单得说：对基于GPL v3协议的源代码，若个人或机构仅仅是自己使用，则可以闭源。
# 若基于该开源代码，开发出程序或衍生产品用于商业行为则也需开源。
# 官方网站：http://www.vnpy.cn

# -*- coding=utf-8 -*-
# 导入CTP行情库
from vnapictpmd import *
# 导入CTP交易库
from vnapictptd import *
# 导入时间库
import time, datetime

md = vnapictpmd()  # 行情接口类赋值给变量
td = vnapictptd()  # 交易接口类赋值给变量

# 限制交易时间
today = datetime.date.today()
gStart1 = datetime.datetime(today.year, today.month, today.day, 9, 30, 0)  # 开盘时间1
gEnd1 = datetime.datetime(today.year, today.month, today.day, 11, 30, 0)  # 收盘时间1
gStart2 = datetime.datetime(today.year, today.month, today.day, 13, 0, 0)  # 开盘时间2
gEnd2 = datetime.datetime(today.year, today.month, today.day, 15, 0, 0)  # 收盘时间2
gExitTime = datetime.datetime(today.year, today.month, today.day, 15, 1, 0)  # 退出运行的时间


def IsStockTrade():
    now = datetime.datetime.now()
    if ((gStart1 < now and now < gEnd1) or
            (gStart2 < now and now < gEnd2)):
        return True
    else:
        return False


# ------------------------------------------MD回调函数、相关变量开始----------------------------------------------
num = 0

# 回调类型
MD_EMPTY = 8000  # 无消息
MD_LOGIN_SCUESS = 8001  # 登录成功
MD_LOGIN_DENIED = 8002  # 登录被拒绝
# MD_LOGIN_ERRORPASSWORD     = 8003 #密码错误(行情没有密码错误)
MD_LOGINOUT_SCUESS = 8004  # 登出成功
MD_NETCONNECT_SCUESS = 8005  # 连接成功
MD_NETCONNECT_BREAK = 8006  # 断开连接
MD_NETCONNECT_FAILER = 8007  # 连接失败
MD_SUBCRIBE_SCUESS = 8008  # 订阅成功
MD_UNSUBCRIBE_SCUESS = 8009  # 取消订阅成功
MD_NEWTICK = 8010  # 新Tick到来
MD_SYSTEM_ERROR = 8011  # 错误应答
MD_QRY_FORQUOTE = 8012  # 询价通知


def MD_OnEmptyCmd():
    # 回调指令缓冲区已为空（因为短时间获得多个指令，时间间隔态度，在下面的for i in range(md.GetUnGetCmdSize()):循环执行了多次已经完成了）
    print("---------------MD_OnEmptyCmd---------------")


def MD_OnUserLogin():
    # 登录成功
    print("---------------MD_OnUserLogin---------------")
    # data = cast(md.GetCmdContent_LoginScuess(), POINTER(VN_CThostFtdcRspUserLoginField))
    data = md.GetCmdContent_LoginScuess()
    print("TradingDay %s" % (str(data[0].TradingDay)))  # 交易日
    print("LoginTime %s" % (str(data[0].LoginTime)))  # 登录成功时间
    print("BrokerID %s" % (str(data[0].BrokerID)))  # 经纪公司代码
    print("UserID %s" % (str(data[0].UserID)))  # 用户代码
    print("SystemName %s" % (str(data[0].SystemName)))  # 交易系统名称
    print("FrontID %s" % (str(data[0].FrontID)))  # 前置编号
    print("SessionID %s" % (str(data[0].SessionID)))  # 会话编号
    print("MaxOrderRef %s" % (str(data[0].MaxOrderRef)))  # 最大报单引用
    print("SHFETime %s" % (str(data[0].SHFETime)))  # 上期所时间
    print("DCETime %s" % (str(data[0].DCETime)))  # 大商所时间
    print("CZCETime %s" % (str(data[0].CZCETime)))  # 郑商所时间
    print("FFEXTime %s" % (str(data[0].FFEXTime)))  # 中金所时间
    print("INETime %s" % (str(data[0].INETime)))  # 能源中心时间


def MD_OnUserLoginDenied():
    # 登录被拒绝
    print("---------------MD_OnUserLoginDenied---------------")


def MD_OnUserLogout():
    # 登出成功
    print("---------------MD_OnUserLogout---------------")
    # data = cast(md.GetCmdContent_LoginOut(), POINTER(VN_CThostFtdcRspUserLoginField))
    data = md.GetCmdContent_LoginOut()
    print("BrokerID %s" % (str(data[0].BrokerID)))  # 期货公司brokeid
    print("UserID %s" % (str(data[0].UserID)))  # 账户


def MD_OnFrontConnected():
    # 与行情服务器连接成功
    # 当客户端与交易后台建立起通信连接时（还未登录前），该方法被调用。
    print("---------------MD_OnFrontConnected---------------")


def MD_OnFrontDisconnected():
    # 与行情服务器断开连接
    # 当客户端与交易后台通信连接断开时，该方法被调用。当发生这个情况后，API会自动重新连接，客户端可不做处理。
    print("---------------MD_OnFrontDisconnected---------------")


def MD_OnFrontConnectedFailer():
    # 连接失败
    print("---------------MD_OnFrontConnectedFailer---------------")


def MD_OnSubMarketData():
    # 订阅成功
    print("---------------MD_OnSubMarketData---------------")
    # data = cast(md.GetCmdContent_SubMarketData(), POINTER(VN_Instrument))
    data = md.GetCmdContent_SubMarketData()
    print("InstrumentID %s" % (str(data[0].InstrumentID)))  # 合约代码


def MD_OnUnSubMarketData():
    # 取消订阅行情成功
    print("---------------MD_OnUnSubMarketData---------------")
    # data = cast(md.GetCmdContent_UnSubMarketData(), POINTER(VN_Instrument))
    data = md.GetCmdContent_UnSubMarketData()
    print("InstrumentID %s" % (str(data[0].InstrumentID)))  # 合约代码


def MD_OnTick():
    # 新的一笔Tick数据驱动
    # print "---------------MD_OnTick---------------"
    global num
    num = num + 1
    # 取得新TICK的合约代码
    Instrument = md.GetCmdContent_Tick()
    # print "Instrument %s"%Instrument
    # 打印该合约数据, 可增加交易策略逻辑计算，计算进程放入其它线程或进程中，以免耗时计算阻塞行情接收和其它回调
    print
    u"(%d)%s %s [%0.02f][%0.00f]" % (
        num, Instrument, md.InstrumentID(Instrument), md.LastPrice(Instrument), md.Volume(Instrument))


def MD_OnError():
    # 错误信息回报
    print("---------------MD_OnRspError---------------")
    # data = cast(md.GetCmdContent_Error(), POINTER(VN_CThostFtdcRspInfoField))
    data = md.GetCmdContent_Error()
    print("ErrorID %s" % (str(data[0].ErrorID)))  # 错误代码
    print("ErrorMsg %s" % (str(data[0].ErrorMsg)))  # 错误信息


def MD_OnForQuote():
    # 询价通知
    print("---------------MD_OnForQuote---------------")
    # data = cast(md.GetCmdContent_Forquote(), POINTER(VN_CThostFtdcForQuoteRspField))
    data = md.GetCmdContent_Forquote()  # POINTER(VN_CThostFtdcForQuoteRspField))
    print("TradingDay %s" % (str(data[0].TradingDay)))  # 交易日
    print("InstrumentID %s" % (str(data[0].InstrumentID)))  # 合约代码
    print("ForQuoteSysID %s" % (str(data[0].ForQuoteSysID)))  # 询价编号
    print("ForQuoteTime %s" % (str(data[0].ForQuoteTime)))  # 询价时间
    print("ActionDay %s" % (str(data[0].ActionDay)))  # 业务日期
    print("ExchangeID %s" % (str(data[0].ExchangeID)))  # 交易所代码


mddict = {
    MD_EMPTY: MD_OnEmptyCmd,
    MD_LOGIN_SCUESS: MD_OnUserLogin,
    MD_LOGIN_DENIED: MD_OnUserLoginDenied,
    MD_LOGINOUT_SCUESS: MD_OnUserLogout,
    MD_NETCONNECT_SCUESS: MD_OnFrontConnected,
    MD_NETCONNECT_BREAK: MD_OnFrontDisconnected,
    MD_NETCONNECT_FAILER: MD_OnFrontConnectedFailer,
    MD_SUBCRIBE_SCUESS: MD_OnSubMarketData,
    MD_UNSUBCRIBE_SCUESS: MD_OnUnSubMarketData,
    MD_NEWTICK: MD_OnTick,
    MD_SYSTEM_ERROR: MD_OnError,
    MD_QRY_FORQUOTE: MD_OnForQuote
}


# ------------------------------------------MD回调函数、相关变量结束----------------------------------------------
# import os
# main()为程序入口函数，所有的行情、交易订阅、指标调用、下单的逻辑均写在此函数内执行
def main():
    print("官方微信公众号从网址 http://www.vnpy.cn 获取  \n");
    # 从配置文件Instrument.ini  读取订阅的合约，每行写一个要订阅行情的合约，用调用ReadInstrument()的方式就无需通过调用Subcribe系列函数方式来订阅合约了，编译成exe后，也方便通过更改配置文件来更改合约
    retLogin = md.ReqUserLogin()  # 调用交易接口元素，通过 “ 接口变量.元素（接口类内部定义的方法或变量） ” 形式调用
    # Login()，不需要参数，Login读取QuickLibTD.ini的配置信息，并登录

    # 返回0， 表示登录成功，
    # 返回1， FutureTDAccount.ini错误
    # 返回2， 登录超时
    print('login: ', retLogin)
    if retLogin == 0:
        print('登陆交易成功')
    else:
        print('登陆交易失败')
    # OrderRef1 = td.InsertOrder('zn1705', VN_D_Buy, VN_OF_Open, VN_OPT_LimitPrice, md.LastPrice('zn1705')+10, 1)
    # OrderRef1 = td.InsertOrder('zn1705', VN_D_Buy, VN_OF_Open, VN_OPT_LimitPrice, md.LastPrice('zn1705')+10, 1)
    # 读取合约订阅的配置文件
    # 设置拒绝接收行情服务器数据的时间，有时候（特别是模拟盘）在早晨6-8点会发送前一天的行情数据，若不拒收的话，会导致历史数据错误，本方法最多可以设置4个时间段进行拒收数据
    # md.SetRejectdataTime(0.0400, 0.0840, 0.1530, 0.2030, NULL, NULL, NULL, NULL);
    # 订阅合约时，请注意合约的大小写，中金所和郑州交易所是大写，上海和大连期货交易所是小写的
    # ReadInstrumentIni(True)调用订阅函数Subcribe，可以用AddPeriod函数添加周期参数
    # 尽量避免使用不到的周期参数，可以节省内存和CPU占用
    # 周期参数：
    # VN_M1    1分钟周期


    # 给au1612添加根据tick生成的周期，尽量避免添加不适用的周期数据，可以降低CPU和内存占用
    # md.AddPeriod('au1612',VN_M10)   #添加M3周期（未在Subcribe、Subcribe1~Subcribe8函数中指定的周期，可以在本函数补充该品种周期，可多次调用函数设置同时保存多个周期）
    # md.AddPeriod('ag1612',VN_M10)  #添加M10周期（未在Subcribe、Subcribe1~Subcribe8函数中指定的周期，可以在本函数补充该品种周期，可多次调用函数设置同时保存多个周期）
    # md.AddPeriod('ag1612',VN_M5)   #添加M5周期（未在Subcribe、Subcribe1~Subcribe8函数中指定的周期，可以在本函数补充该品种周期，可多次调用函数设置同时保存多个周期）
    # md.AddPeriod('zn1705',VN_ALL)  #添加所有M1、M3、M5、M10、M15、M30、M60、M120、D1周期（未在Subcribe、Subcribe1~Subcribe8函数中指定的周期，可以在本函数补充该品种周期，可多次调用函数设置同时保存多个周期）

    # 保存Tick数据,参数1表示简单模式
    # md.SaveTick(2)

    #print('number:', md.InstrumentNum)
    # main()函数内的以上部分代码只执行一次，以下while(1)循环内的代码，会一直循环执行，可在这个循环内需增加策略判断，达到下单条件即可下单
    while (1):  # 死循环，反复执行
        # if md.OnTick():  #判断是否有新Tick数据，while循环不需要Sleep,当没有新Tick时，会处在阻塞状态
        # print(u"A New Tick\n");
        # name=md.GetTickInstrument() #取新TICK合约名称
        # print u"名称：%s"%name
        # print(name,md.InstrumentID(name), md.LastPrice(name)) #打印该合约数据

        print(u"Wait for a New Cmd(MD)\n");
        # 判断是否有新Tick数据，while循环不需要Sleep,当没有新Tick时，会处在阻塞状态
        mddict[md.OnCmd()]()
        print(u"Get A New cmd(MD)\n");
        # 判断当前时间是否在交易时间内，如果在返回真，则开始执行
        # if (IsStockTrade()):

        # 下单方式（1）
        #                                品种代码    多空方向    开仓还是平仓  市价或现价   价格  下单数量
        # 下单函数原型 InsertOrder(self, instrumentID, direction,  offsetFlag, priceType,  price,   num):
        OrderRef = td.InsertOrder('zn1705', VN_D_Buy, VN_OF_Open, VN_OPT_LimitPrice,
                                      md.LastPrice('zn2405') + 10, 1)
        # 交易记录写日志文件
        # md.LogFile(u'traderecord.csv',u'交易下单  zn1705  VN_D_Buy')
        time.sleep(1)
        # 撤销所有未成交的委托单
        ret = td.DeleteOrder('zn1705', OrderRef)

        # 对该单号成交的品种设置动态止损，AddStopMonitor未演示，等待封装完成
        # 品种    #单号  #止损方式(动态止损VN_Dynamic,固定价格止损VN_Static ) #止损阈值百分比 3表示离上一次最高点或最低点反向绝对值为3%止损
        # md.AddStopMonitor('zn1705', OrderRef,VN_Dynamic,3)

        # 下单方式（2）
        #                             品种代码    多空方向    开仓还是平仓  市价或现价   价格  资金比例   资金类型(动态权益VN_Dynamic_Capital、静态权益VN_Static_Capital)  合约乘数
        # 下单函数原型 InsertOrder(self, instrumentID, direction,  offsetFlag, priceType,  price,   rate):
        OrderRef = td.InsertOrderByRate('zn1705', VN_D_Buy, VN_OF_Open, VN_OPT_LimitPrice,
                                            md.LastPrice('zn1705') + 10, 0.25, VN_Dynamic_Capital, 5)
        # 交易记录写日志文件
        # md.LogFile(u'traderecord.csv',u'交易下单  zn1705  VN_D_Buy')
        time.sleep(1)
        # 撤销所有未成交的委托单
        ret = td.DeleteOrder('zn1705', OrderRef)

        # 对该单号成交的品种设置动态止损，AddStopMonitor未演示，等待封装完成
        # 品种    #单号  #止损方式(动态止损VN_Dynamic,固定价格止损VN_Static ) #止损阈值百分比 3表示离上一次最高点或最低点反向绝对值为3%止损
    # md.AddStopMonitor('zn1705', OrderRef,VN_Dynamic,3)


if __name__ == '__main__':
    main()
